package com.efast.cafe.portal.security.core;

import com.efast.cafe.framework.constant.CommonConstant;
import com.efast.cafe.framework.constant.SecurityConstant;
import com.efast.cafe.framework.enums.ResultStatus;
import com.efast.cafe.framework.redis.ObjectRedisTemplate;
import com.efast.cafe.framework.redis.locker.LockerFactory;
import com.efast.cafe.framework.redis.locker.RedissonDistributedLocker;
import com.efast.cafe.framework.spring.SpringContextUtils;
import com.efast.cafe.portal.bean.common.PortalUserDetails;
import com.efast.cafe.portal.bean.common.PortalUserSecurityBean;
import com.efast.cafe.portal.security.core.impl.LdapAuthenticationProcessor;
import com.efast.cafe.portal.security.core.impl.PwdAuthenticationProcessor;
import com.efast.cafe.portal.security.core.impl.WechatAuthenticationProcessor;
import com.efast.cafe.util.ConfigUtil;
import com.efast.cafe.util.StringUtil;
import net.sf.json.JSONObject;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.session.FindByIndexNameSessionRepository;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Set;
import java.util.concurrent.TimeUnit;

public class AuthenticationFactory {

    //登录方式参数
    public static final String SPRING_SECURITY_LOGIN_TYPE = "loginType";

    public static final String SPRING_SECURITY_FORM_COMPANYCODE_KEY = "companyCode";
    public static final String SPRING_SECURITY_FORM_UNAME_KEY = "username";
    public static final String SPRING_SECURITY_FORM_PWD_KEY = "password";
    static final String DEFAULT_SPRING_SESSION_REDIS_PREFIX = "spring:session:";
    static final String PRINCIPAL_NAME_INDEX_NAME = FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME;
    //是否允许多地登录
    static boolean manysession = Boolean.parseBoolean(ConfigUtil.getValue("security.manysession"));

    public static boolean isManysession() {
        return manysession;
    }

    /**
     * @param @param  request
     * @param @param  response
     * @param @return 参数
     * @return UsernamePasswordAuthenticationToken    返回类型
     * @throws
     * @Title: authenticationToken
     * @Description: 登录拦截器
     */
    public static UsernamePasswordAuthenticationToken authenticationToken(HttpServletRequest request, HttpServletResponse response) {

        AuthenticationTypeService service = getAuthenticationService(request);
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = service.authentication(request);//校验参数
        if (!manysession) {
            //相同账号登录限制并发。
            String lockKey = "locker:appLogin:" + usernamePasswordAuthenticationToken.getName();
            RedissonDistributedLocker locker = LockerFactory.getLocker("Portal");
            boolean locked = false;
            try {
                //相同账号登录 并发限制 最多等待10秒，获取到锁最多保留60秒 60秒后自动释放锁
                locked = locker.tryLock(lockKey, 10, 60, TimeUnit.SECONDS);
                if (locked) {
                    return usernamePasswordAuthenticationToken;
                } else {
                    JSONObject jo = new JSONObject();
                    jo.put("success", false);
                    jo.put("errorCode", ResultStatus.SYS_BUSY.getCode());
                    jo.put("message", "当前用户并发登录过多 ，请稍后再试。");
                    response.setCharacterEncoding("UTF-8");
                    response.setContentType("application/json; charset=utf-8");
                    try {
                        response.getWriter().print(jo.toString());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    return null;
                }

            } finally {
                if (locker != null && locked)
                    locker.unlock(lockKey);
            }
        } else {
            return usernamePasswordAuthenticationToken;
        }

    }


    /**
     * @param @param request
     * @param @param response
     * @param @param authentication    参数
     * @return void    返回类型
     * @throws
     * @Title: onAuthenticationSuccess
     * @Description: 登录成功
     */
    public static void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                               Authentication authentication) throws IOException, ServletException {
        if (!isManysession()) {
            PortalUserDetails userDetail = (PortalUserDetails) authentication.getPrincipal();
            PortalUserSecurityBean user = userDetail.getUser();
            ObjectRedisTemplate objectRedisTemplate = SpringContextUtils.getBean(ObjectRedisTemplate.class);
            String key = new StringBuilder(DEFAULT_SPRING_SESSION_REDIS_PREFIX)
                    .append("index:").append(PRINCIPAL_NAME_INDEX_NAME)
                    .append(":").append(user.getUsername()).toString();
            Set<Object> sessionIds = objectRedisTemplate.boundSetOps(key)
                    .members();
            //设置已登录用户的session 为失效。
            sessionIds.forEach(sid -> {
                String otherSid = (String) sid;
                if (!request.getSession().getId().equals(otherSid)) {
                    objectRedisTemplate.boundSetOps(key).remove(otherSid);
                    String skey = DEFAULT_SPRING_SESSION_REDIS_PREFIX + "sessions:" + otherSid;
                    objectRedisTemplate.boundHashOps(skey).
                            put("sessionAttr:" + CommonConstant.CURRENT_LOGIN_USER_FLAG, false);
                    objectRedisTemplate.boundHashOps(skey).expire(30, TimeUnit.MINUTES);
                }

            });
        }
        getAuthenticationService(request).onAuthenticationSuccess(request, response, authentication);
    }

    /**
     * @param @param request
     * @param @param response
     * @param @param exception    参数
     * @return void    返回类型
     * @throws
     * @Title: onAuthenticationFailure
     * @Description: 登录失败
     */
    public static void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                               AuthenticationException exception) throws IOException, ServletException {
        getAuthenticationService(request).onAuthenticationFailure(request, response, exception);
    }

    /**
     * @param @param  request
     * @param @param  response
     * @param @param  authentication
     * @param @throws IOException
     * @param @throws ServletException    参数
     * @return void    返回类型
     * @throws
     * @Title: onLogoutSuccess
     * @Description: 登出成功
     */
    public static void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
                                       Authentication authentication) throws IOException, ServletException {
        getAuthenticationService(request).onLogoutSuccess(request, response, authentication);
    }


    private static AuthenticationTypeService getAuthenticationService(HttpServletRequest request) {
        String loginType = request.getParameter(SPRING_SECURITY_LOGIN_TYPE);
        if (StringUtil.isNullOrBlank(loginType) && isWechat(request)) {
            loginType = SecurityConstant.LoginType.LoginType_Wechat;
        }
        if (StringUtil.isNullOrBlank(loginType) || SecurityConstant.LoginType.LoginType_Pwd.equals(loginType)) {
            return SpringContextUtils.getBean("pwdAuthenticationProcessor", PwdAuthenticationProcessor.class);
        } else if (SecurityConstant.LoginType.LoginType_Wechat.equals(loginType)) {
            return SpringContextUtils.getBean("wechatAuthenticationProcessor", WechatAuthenticationProcessor.class);
        } else if (SecurityConstant.LoginType.LDAP_LOGIN.equals(loginType)) {
            return SpringContextUtils.getBean(LdapAuthenticationProcessor.class);
        } else {
            throw new AuthenticationServiceException(SecurityConstant.ERROR_CODE.LOGIN_TYPE_ERROR);
        }
    }

    private static boolean isWechat(HttpServletRequest request) {
        String ua = request.getHeader("User-Agent").toLowerCase();
        if (ua.indexOf("micromessenger") > -1) {
            return true;//微信
        }
        return false;//非微信手机浏览器
    }
}
